001 /*
002 /*
003 * Copyright (c) 2005 Stephen J. McConnell
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.
015 *
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020 package net.dpml.component;
021
022 import java.io.File;
023 import java.lang.reflect.Constructor;
024 import java.lang.reflect.InvocationHandler;
025 import java.lang.reflect.Method;
026 import java.lang.reflect.Proxy;
027 import java.net.URI;
028 import java.util.EventObject;
029 import java.util.EventListener;
030
031 import net.dpml.lang.Part;
032 import net.dpml.lang.Plugin;
033
034 import net.dpml.util.DefaultLogger;
035
036 /**
037 * The CompositionControllerContext class wraps a ContentModel and supplies convinience
038 * operations that translate ContentModel properties and events to type-safe values used
039 * in the conposition controller.
040 *
041 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
042 * @version 1.0.0
043 */
044 public final class InitialContext extends LocalEventProducer
045 implements ControllerContext, Disposable
046 {
047 //----------------------------------------------------------------------------
048 // static
049 //----------------------------------------------------------------------------
050
051 /**
052 * Create the default controller using the default initial context.
053 * The default context and associated controller disposal will be triggered
054 * on JVM shutdown.
055 *
056 * @return the default controller
057 */
058 public static Controller createController()
059 {
060 return createController( null );
061 }
062
063 /**
064 * Create the default controller. Controller disposal is the responsiblity
065 * of the client application.
066 *
067 * @param context the controller context
068 * @return the controller
069 */
070 public static Controller createController( final InitialContext context )
071 {
072 ControllerInvocationHandler handler =
073 new ControllerInvocationHandler( context );
074 return (Controller) Proxy.newProxyInstance(
075 Controller.class.getClassLoader(), new Class[]{Controller.class}, handler );
076 }
077
078 /**
079 * Internal invocation handler that delays controller instantiation
080 * until a request against the controller is made by a client.
081 */
082 private static final class ControllerInvocationHandler implements InvocationHandler
083 {
084 private InitialContext m_context;
085 private Controller m_controller;
086
087 private ControllerInvocationHandler( InitialContext context )
088 {
089 m_context = context;
090 }
091
092 /**
093 * Invoke the specified method on underlying object.
094 * This is called by the proxy object.
095 *
096 * @param proxy the proxy object
097 * @param method the method invoked on proxy object
098 * @param args the arguments supplied to method
099 * @return the return value of method
100 * @throws Throwable if an error occurs
101 */
102 public Object invoke(
103 final Object proxy, final Method method, final Object[] args ) throws Throwable
104 {
105 Controller controller = getController();
106 return method.invoke( controller, args );
107 }
108
109 private synchronized Controller getController()
110 {
111 if( null != m_controller )
112 {
113 return m_controller;
114 }
115 else
116 {
117 m_controller = InitialContext.newController( m_context );
118 return m_controller;
119 }
120 }
121 }
122
123 private static URI getControllerURI() throws Exception
124 {
125 String spec =
126 System.getProperty(
127 "dpml.part.controller.uri",
128 "artifact:part:dpml/metro/dpml-metro-runtime#1.0.0" );
129 return new URI( spec );
130 }
131
132 /**
133 * Construct a controller.
134 * @param context the controller context
135 * @return the controller
136 */
137 private static Controller newController( final InitialContext context )
138 {
139 InitialContext control = context;
140 if( null == control )
141 {
142 control = new InitialContext();
143 Runtime.getRuntime().addShutdownHook( new ContextShutdownHook( control ) );
144 }
145
146 ClassLoader classloader = Thread.currentThread().getContextClassLoader();
147 try
148 {
149 Thread.currentThread().setContextClassLoader( InitialContext.class.getClassLoader() );
150 URI uri = getControllerURI();
151 Part part = Part.load( uri );
152 if( part instanceof Plugin )
153 {
154 Plugin plugin = (Plugin) part;
155 Class c = plugin.getPluginClass();
156 Constructor constructor = c.getConstructor( new Class[]{ControllerContext.class} );
157 return (Controller) constructor.newInstance( new Object[]{control} );
158 }
159 else
160 {
161 return (Controller) part.instantiate( new Object[]{control} );
162 }
163 }
164 catch( Throwable e )
165 {
166 final String error =
167 "Internal error while attempting to establish the standard controller.";
168 throw new RuntimeException( error, e );
169 }
170 finally
171 {
172 Thread.currentThread().setContextClassLoader( classloader );
173 }
174 }
175
176 //----------------------------------------------------------------------------
177 // state
178 //----------------------------------------------------------------------------
179
180 /**
181 * Working directory.
182 */
183 private File m_work;
184
185 /**
186 * Temp directory.
187 */
188 private File m_temp;
189
190 /**
191 * The assigned partition name.
192 */
193 private String m_partition;
194
195 //----------------------------------------------------------------------------
196 // constructor
197 //----------------------------------------------------------------------------
198
199 /**
200 * Creation of a new <tt>InitialContext</tt>.
201 */
202 public InitialContext()
203 {
204 this( "", null, null );
205 }
206
207 /**
208 * Creation of a new <tt>InitialContext</tt>.
209 * @param partition the assigned partition name
210 */
211 public InitialContext( String partition )
212 {
213 this( partition, null, null );
214 }
215
216 /**
217 * Creation of a new <tt>InitialContext</tt>.
218 * @param partition the partition name
219 * @param work the working directory
220 * @param temp the temporary directory
221 */
222 public InitialContext( String partition, File work, File temp )
223 {
224 super( new DefaultLogger( partition ) );
225
226 m_partition = partition;
227
228 if( null == work )
229 {
230 String path = System.getProperty( "user.dir" );
231 m_work = new File( path );
232 }
233 else
234 {
235 m_work = work;
236 }
237
238 if( null == temp )
239 {
240 String path = System.getProperty( "java.io.tmpdir" );
241 m_temp = new File( path );
242 }
243 else
244 {
245 m_temp = temp;
246 }
247 }
248
249 //----------------------------------------------------------------------------
250 // Disposable
251 //----------------------------------------------------------------------------
252
253 /**
254 * Initiate disposal.
255 */
256 public void dispose()
257 {
258 ControllerDisposalEvent event = new ControllerDisposalEvent( this );
259 enqueueEvent( event, false );
260 super.dispose();
261 }
262
263 //----------------------------------------------------------------------------
264 // ControllerContext
265 //----------------------------------------------------------------------------
266
267 /**
268 * Return the partition name
269 * @return the partion name
270 */
271 public String getPartition()
272 {
273 return m_partition;
274 }
275
276 /**
277 * Return the root working directory.
278 *
279 * @return directory representing the root of the working directory hierachy
280 */
281 public File getWorkingDirectory()
282 {
283 synchronized( getLock() )
284 {
285 return m_work;
286 }
287 }
288
289 /**
290 * Set the root working directory.
291 *
292 * @param dir the root of the working directory hierachy
293 */
294 public void setWorkingDirectory( File dir )
295 {
296 synchronized( getLock() )
297 {
298 m_work = dir;
299 }
300 }
301
302 /**
303 * Return the root temporary directory.
304 *
305 * @return directory representing the root of the temporary directory hierachy.
306 */
307 public File getTempDirectory()
308 {
309 synchronized( getLock() )
310 {
311 return m_temp;
312 }
313 }
314
315 /**
316 * Add the supplied controller context listener to the controller context. A
317 * controller implementation should not maintain strong references to supplied
318 * listeners.
319 *
320 * @param listener the controller context listener to add
321 */
322 public void addControllerContextListener( ControllerContextListener listener )
323 {
324 addListener( listener );
325 }
326
327 /**
328 * Remove the supplied controller context listener from the controller context.
329 *
330 * @param listener the controller context listener to remove
331 */
332 public void removeControllerContextListener( ControllerContextListener listener )
333 {
334 removeListener( listener );
335 }
336
337 //----------------------------------------------------------------------------
338 // impl
339 //----------------------------------------------------------------------------
340
341 /**
342 * Process a context related event.
343 * @param event the event to process
344 */
345 protected void processEvent( EventObject event )
346 {
347 if( event instanceof WorkingDirectoryChangeEvent )
348 {
349 processWorkingDirectoryChangeEvent( (WorkingDirectoryChangeEvent) event );
350 }
351 else if( event instanceof TempDirectoryChangeEvent )
352 {
353 processTempDirectoryChangeEvent( (TempDirectoryChangeEvent) event );
354 }
355 else if( event instanceof ControllerDisposalEvent )
356 {
357 processControllerDisposalEvent( (ControllerDisposalEvent) event );
358 }
359 }
360
361 private void processWorkingDirectoryChangeEvent( WorkingDirectoryChangeEvent event )
362 {
363 EventListener[] listeners = super.listeners();
364 for( int i=0; i<listeners.length; i++ )
365 {
366 EventListener eventListener = listeners[i];
367 if( eventListener instanceof ControllerContextListener )
368 {
369 ControllerContextListener listener = (ControllerContextListener) eventListener;
370 try
371 {
372 listener.workingDirectoryChanged( event );
373 }
374 catch( Throwable e )
375 {
376 final String error =
377 "ControllerContextListener working dir change notification error.";
378 getInternalLogger().error( error, e );
379 }
380 }
381 }
382 }
383
384 private void processTempDirectoryChangeEvent( TempDirectoryChangeEvent event )
385 {
386 EventListener[] listeners = super.listeners();
387 for( int i=0; i<listeners.length; i++ )
388 {
389 EventListener eventListener = listeners[i];
390 if( eventListener instanceof ControllerContextListener )
391 {
392 ControllerContextListener listener = (ControllerContextListener) eventListener;
393 try
394 {
395 listener.tempDirectoryChanged( event );
396 }
397 catch( Throwable e )
398 {
399 final String error =
400 "ControllerContextListener temp dir change notification error.";
401 getInternalLogger().error( error, e );
402 }
403 }
404 }
405 }
406
407 private void processControllerDisposalEvent( ControllerDisposalEvent event )
408 {
409 EventListener[] listeners = super.listeners();
410 for( int i=0; i<listeners.length; i++ )
411 {
412 EventListener eventListener = listeners[i];
413 if( eventListener instanceof ControllerContextListener )
414 {
415 ControllerContextListener listener = (ControllerContextListener) eventListener;
416 try
417 {
418 listener.controllerDisposal( event );
419 }
420 catch( Throwable e )
421 {
422 final String error =
423 "ControllerContextListener disposal notification error.";
424 getInternalLogger().error( error, e );
425 }
426 }
427 }
428 }
429
430 //----------------------------------------------------------------------------
431 // static (private)
432 //----------------------------------------------------------------------------
433
434 /**
435 * Working directory change event impl.
436 */
437 private static class WorkingDirectoryChangeEvent extends ControllerContextEvent
438 {
439 public WorkingDirectoryChangeEvent(
440 ControllerContext source, File oldDir, File newDir )
441 {
442 super( source, oldDir, newDir );
443 }
444 }
445
446 /**
447 * Temp directory change event impl.
448 */
449 private static class TempDirectoryChangeEvent extends ControllerContextEvent
450 {
451 public TempDirectoryChangeEvent(
452 ControllerContext source, File oldDir, File newDir )
453 {
454 super( source, oldDir, newDir );
455 }
456 }
457
458 /**
459 * Disposal event.
460 */
461 private static class ControllerDisposalEvent extends ControllerContextEvent
462 {
463 public ControllerDisposalEvent( ControllerContext source )
464 {
465 super( source, null, null );
466 }
467 }
468
469 /**
470 * Internal utility class to handle context disposal on JVM shutdown.
471 */
472 private static class ContextShutdownHook extends Thread
473 {
474 private InitialContext m_context;
475
476 /**
477 * Creation of a new initial context shutdown hook.
478 * @param context the initional context to be shutdown on JVM shutdown
479 */
480 ContextShutdownHook( InitialContext context )
481 {
482 m_context = context;
483 }
484
485 /**
486 * Execute context disposal.
487 */
488 public void run()
489 {
490 try
491 {
492 m_context.dispose();
493 }
494 catch( Throwable e )
495 {
496 boolean ignorable = true;
497 }
498 finally
499 {
500 System.runFinalization();
501 }
502 }
503 }
504 }